package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"gollect/monitor/db"
	monproto "gollect/proto"

	"github.com/gorilla/websocket"
)

// ClientInfo holds information about a WebSocket client
type ClientInfo struct {
	conn      *websocket.Conn
	writeMux  sync.Mutex
	connected bool
}

// WebServer represents the HTTP server for the monitoring dashboard
type WebServer struct {
	server      *http.Server
	trodes      map[string]*TrodeStatus
	trodesMux   sync.RWMutex
	chartData   *ChartData
	chartMux    sync.RWMutex
	processData *ProcessData
	userData    *UserData
	dataMux     sync.RWMutex
	interval    time.Duration
	db          *db.DB

	// WebSocket support
	upgrader   websocket.Upgrader
	clients    map[*websocket.Conn]*ClientInfo
	clientsMux sync.RWMutex
}

// TrodeStatus represents the status of a monitoring trode
type TrodeStatus struct {
	ID               string               `json:"id"`
	Address          string               `json:"address"`
	Port             int                  `json:"port"`
	Online           bool                 `json:"online"`
	CPU              float64              `json:"cpu"`
	Memory           float64              `json:"memory"`
	Disk             float64              `json:"disk"`
	LastUpdate       time.Time            `json:"last_update"`
	LastError        string               `json:"last_error"`
	Processes        []*monproto.JobInfo  `json:"processes"`
	Users            []*monproto.UserInfo `json:"users"`
	ProcessTimestamp time.Time            `json:"process_timestamp"`
	UserTimestamp    time.Time            `json:"user_timestamp"`
}

// ProcessData represents the latest process information
type ProcessData struct {
	Timestamp time.Time           `json:"timestamp"`
	Processes []*monproto.JobInfo `json:"processes"`
}

// UserData represents the latest user information
type UserData struct {
	Timestamp time.Time            `json:"timestamp"`
	Users     []*monproto.UserInfo `json:"users"`
}

// ChartData represents the data for charts
type ChartData struct {
	CPU     *ChartSeries `json:"cpu"`
	Memory  *ChartSeries `json:"memory"`
	Disk    *ChartSeries `json:"disk"`
	Network *NetworkData `json:"network"`
}

// ChartSeries represents a time series for charts
type ChartSeries struct {
	Timestamps []string  `json:"timestamps"`
	Values     []float64 `json:"values"`
}

// NetworkData represents network I/O data
type NetworkData struct {
	Timestamps []string  `json:"timestamps"`
	Sent       []float64 `json:"sent"`
	Received   []float64 `json:"received"`
}

// DashboardData represents the data sent to the dashboard template
type DashboardData struct {
	Trodes          []*TrodeStatus `json:"trodes"`
	ActiveTrodes    int            `json:"active_trodes"`
	AvgCPU          float64        `json:"avg_cpu"`
	AvgMemory       float64        `json:"avg_memory"`
	AvgDisk         float64        `json:"avg_disk"`
	ChartData       *ChartData     `json:"chart_data"`
	ProcessData     *ProcessData   `json:"process_data"`
	UserData        *UserData      `json:"user_data"`
	Interval        int            `json:"interval"`         // ms
	IntervalSeconds int            `json:"interval_seconds"` // s
}

// WebSocketMessage represents a message sent over WebSocket
type WebSocketMessage struct {
	Type      string      `json:"type"`
	Payload   interface{} `json:"payload"`
	Timestamp time.Time   `json:"timestamp"`
}

// WebSocket message types
const (
	MessageTypeTrodesUpdate = "trodes_update"
	MessageTypeChartsUpdate = "charts_update"
	MessageTypePing         = "ping"
	MessageTypePong         = "pong"
)

// CORS middleware function
func corsMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// Set CORS headers
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		
		// Handle preflight requests
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}
		
		// Call the next handler
		next(w, r)
	}
}

// NewWebServer creates a new web server instance
func NewWebServer(port int, interval time.Duration, dbPath string) (*WebServer, error) {
	// Initialize database
	database, err := db.New(dbPath)
	if err != nil {
		return nil, fmt.Errorf("failed to initialize database: %w", err)
	}

	ws := &WebServer{
		trodes: make(map[string]*TrodeStatus),
		chartData: &ChartData{
			CPU: &ChartSeries{
				Timestamps: make([]string, 0),
				Values:     make([]float64, 0),
			},
			Memory: &ChartSeries{
				Timestamps: make([]string, 0),
				Values:     make([]float64, 0),
			},
			Disk: &ChartSeries{
				Timestamps: make([]string, 0),
				Values:     make([]float64, 0),
			},
			Network: &NetworkData{
				Timestamps: make([]string, 0),
				Sent:       make([]float64, 0),
				Received:   make([]float64, 0),
			},
		},
		processData: &ProcessData{},
		userData:    &UserData{},
		interval:    interval,
		db:          database,
		upgrader: websocket.Upgrader{
			CheckOrigin: func(r *http.Request) bool {
				return true // Allow all origins for development
			},
		},
		clients: make(map[*websocket.Conn]*ClientInfo),
	}

	mux := http.NewServeMux()

	// Main dashboard (now only serves JSON)
	mux.HandleFunc("/", corsMiddleware(ws.handleDashboard))

	// WebSocket endpoint
	mux.HandleFunc("/ws", ws.handleWebSocket)

	// API endpoints with CORS middleware
	mux.HandleFunc("/api/dashboard", corsMiddleware(ws.handleAPIDashboard))
	mux.HandleFunc("/api/refresh", corsMiddleware(ws.handleAPIRefresh))
	mux.HandleFunc("/api/trodes", corsMiddleware(ws.handleAPITrodes))
	mux.HandleFunc("/api/trodes/list", corsMiddleware(ws.handleAPITrodesList))
	mux.HandleFunc("/api/trodes/config", corsMiddleware(ws.handleAPITrodesConfig))
	mux.HandleFunc("/api/trodes/update", corsMiddleware(ws.handleAPITrodeUpdate))
	mux.HandleFunc("/api/charts", corsMiddleware(ws.handleAPICharts))
	mux.HandleFunc("/api/processes", corsMiddleware(ws.handleAPIProcesses))
	mux.HandleFunc("/api/users", corsMiddleware(ws.handleAPIUsers))
	mux.HandleFunc("/api/nodes/validate", corsMiddleware(ws.handleAPINodeValidation))
	mux.HandleFunc("/api/nodes", corsMiddleware(ws.handleAPINodes))
	mux.HandleFunc("/api/nodes/", corsMiddleware(ws.handleAPINode))

	ws.server = &http.Server{
		Addr:    fmt.Sprintf(":%d", port),
		Handler: mux,
	}

	// Start cleanup goroutine
	go ws.cleanupRoutine()

	return ws, nil
}

// cleanupRoutine periodically cleans up old data
func (ws *WebServer) cleanupRoutine() {
	ticker := time.NewTicker(24 * time.Hour) // Run once per day
	defer ticker.Stop()

	for range ticker.C {
		if err := ws.db.CleanupOldData(180 * 24 * time.Hour); err != nil { // Keep 180 days (6 months) of data
			LogError("Error cleaning up old data: %v", err)
		}
	}
}

// getClientInfo safely retrieves client info with read lock
func (ws *WebServer) getClientInfo(conn *websocket.Conn) *ClientInfo {
	ws.clientsMux.RLock()
	defer ws.clientsMux.RUnlock()
	return ws.clients[conn]
}

// broadcastToClients sends a message to all connected WebSocket clients
func (ws *WebServer) broadcastToClients(message *WebSocketMessage) {
	ws.clientsMux.RLock()
	defer ws.clientsMux.RUnlock()

	if len(ws.clients) == 0 {
		return
	}

	// Format message for better readability
	var messageText string
	switch message.Type {
	case MessageTypeTrodesUpdate:
		if data, ok := message.Payload.(*DashboardData); ok {
			messageText = fmt.Sprintf("Trodes Update - Active: %d, Avg CPU: %.1f%%, Avg Memory: %.1f%%, Avg Disk: %.1f%%",
				data.ActiveTrodes, data.AvgCPU, data.AvgMemory, data.AvgDisk)
		} else {
			messageText = fmt.Sprintf("Trodes Update - Type: %s", message.Type)
		}
	case MessageTypeChartsUpdate:
		if data, ok := message.Payload.(*ChartData); ok {
			cpuCount := len(data.CPU.Values)
			memCount := len(data.Memory.Values)
			diskCount := len(data.Disk.Values)
			messageText = fmt.Sprintf("Charts Update - CPU: %d points, Memory: %d points, Disk: %d points",
				cpuCount, memCount, diskCount)
		} else {
			messageText = fmt.Sprintf("Charts Update - Type: %s", message.Type)
		}
	default:
		messageText = fmt.Sprintf("Message - Type: %s", message.Type)
	}

	LogVerbose("[WebSocket] Broadcasting: %s", messageText)

	messageJSON, err := json.Marshal(message)
	if err != nil {
		LogError("Error marshaling WebSocket message: %v", err)
		return
	}

	// Send to all connected clients
	for _, clientInfo := range ws.clients {
		if !clientInfo.connected {
			continue
		}

		clientInfo.writeMux.Lock()
		err := clientInfo.conn.WriteMessage(websocket.TextMessage, messageJSON)
		clientInfo.writeMux.Unlock()

		if err != nil {
			LogError("Error sending WebSocket message: %v", err)
			// Mark client as disconnected
			clientInfo.connected = false
			// Remove disconnected client
			ws.clientsMux.RUnlock()
			ws.clientsMux.Lock()
			delete(ws.clients, clientInfo.conn)
			clientInfo.conn.Close()
			ws.clientsMux.Unlock()
			ws.clientsMux.RLock()
		}
	}
}

// broadcastTrodesUpdate sends trode updates to all connected clients
func (ws *WebServer) broadcastTrodesUpdate() {
	data := ws.getDashboardData()
	message := &WebSocketMessage{
		Type:      MessageTypeTrodesUpdate,
		Payload:   data,
		Timestamp: time.Now(),
	}
	ws.broadcastToClients(message)
}

// broadcastChartsUpdate sends chart updates to all connected clients
func (ws *WebServer) broadcastChartsUpdate() {
	ws.chartMux.RLock()
	chartData := ws.chartData
	ws.chartMux.RUnlock()

	message := &WebSocketMessage{
		Type:      MessageTypeChartsUpdate,
		Payload:   chartData,
		Timestamp: time.Now(),
	}
	ws.broadcastToClients(message)
}

// UpdateTrodeStatus updates the status of a trode
func (ws *WebServer) UpdateTrodeStatus(address string, online bool, data *monproto.MonitoringEnvelope, err error) {
	ws.trodesMux.Lock()
	defer ws.trodesMux.Unlock()

	trode, exists := ws.trodes[address]
	if !exists {
		// Extract port from address
		parts := strings.Split(address, ":")
		port := 50051
		if len(parts) > 1 {
			if p, err := strconv.Atoi(parts[1]); err == nil {
				port = p
			}
		}

		trode = &TrodeStatus{
			ID:         fmt.Sprintf("trode-%d", len(ws.trodes)+1),
			Address:    address,
			Port:       port,
			Online:     false,
			LastUpdate: time.Now(),
		}
		ws.trodes[address] = trode

		// Store trode in database
		if err := ws.db.UpsertTrode(trode.ID, address, port); err != nil {
			LogError("Error storing trode in database: %v", err)
		}
	}

	trode.Online = online
	trode.LastUpdate = time.Now()

	if err != nil {
		trode.LastError = err.Error()
		trode.Online = false
	} else {
		trode.LastError = ""
		trode.Online = true

		// Extract metrics from monitoring data
		ws.extractMetrics(trode, data)
		ws.updateChartData(trode, data)
		ws.updateTrodeData(trode, data)
	}

	// Broadcast updates to WebSocket clients
	go ws.broadcastTrodesUpdate()
}

// extractMetrics extracts metrics from monitoring data
func (ws *WebServer) extractMetrics(trode *TrodeStatus, data *monproto.MonitoringEnvelope) {
	switch payload := data.Payload.(type) {
	case *monproto.MonitoringEnvelope_Cpu:
		trode.CPU = float64(payload.Cpu.TotalLoad * 100)
	case *monproto.MonitoringEnvelope_Memory:
		trode.Memory = float64(payload.Memory.UsedPercent * 100)
	case *monproto.MonitoringEnvelope_Disk:
		// Look for the root partition ("/")
		var value float64
		for _, partition := range payload.Disk.Partitions {
			if partition.Mountpoint == "/" {
				value = float64(partition.UsedPercent * 100)
				break
			}
		}
		// If root partition not found, fall back to first partition
		if value == 0 && len(payload.Disk.Partitions) > 0 {
			value = float64(payload.Disk.Partitions[0].UsedPercent * 100)
		}

		if value > 0 {
			trode.Disk = value
		}
	}
}

// updateChartData updates the chart data with new monitoring data
func (ws *WebServer) updateChartData(trode *TrodeStatus, data *monproto.MonitoringEnvelope) {
	ws.chartMux.Lock()
	defer ws.chartMux.Unlock()

	timestamp := time.Unix(0, data.Timestamp)
	formattedTime := timestamp.Format("15:04:05")

	// Debug log for timestamp
	LogVerbose("[updateChartData] Received envelope with Timestamp: %d (%s)", data.Timestamp, formattedTime)

	switch payload := data.Payload.(type) {
	case *monproto.MonitoringEnvelope_Cpu:
		value := float64(payload.Cpu.TotalLoad * 100)
		ws.chartData.CPU.Timestamps = append(ws.chartData.CPU.Timestamps, formattedTime)
		ws.chartData.CPU.Values = append(ws.chartData.CPU.Values, value)
		ws.limitChartData(ws.chartData.CPU)

		// Store in database
		if err := ws.db.InsertMetric(trode.ID, "cpu", value, timestamp); err != nil {
			LogError("Error storing CPU metric: %v", err)
		}

	case *monproto.MonitoringEnvelope_Memory:
		value := float64(payload.Memory.UsedPercent * 100)
		ws.chartData.Memory.Timestamps = append(ws.chartData.Memory.Timestamps, formattedTime)
		ws.chartData.Memory.Values = append(ws.chartData.Memory.Values, value)
		ws.limitChartData(ws.chartData.Memory)

		// Store in database
		if err := ws.db.InsertMetric(trode.ID, "memory", value, timestamp); err != nil {
			LogError("Error storing memory metric: %v", err)
		}

	case *monproto.MonitoringEnvelope_Disk:
		// Look for the root partition ("/")
		var value float64
		for _, partition := range payload.Disk.Partitions {
			if partition.Mountpoint == "/" {
				value = float64(partition.UsedPercent * 100)
				break
			}
		}
		// If root partition not found, fall back to first partition
		if value == 0 && len(payload.Disk.Partitions) > 0 {
			value = float64(payload.Disk.Partitions[0].UsedPercent * 100)
		}

		if value > 0 {
			ws.chartData.Disk.Timestamps = append(ws.chartData.Disk.Timestamps, formattedTime)
			ws.chartData.Disk.Values = append(ws.chartData.Disk.Values, value)
			ws.limitChartData(ws.chartData.Disk)

			// Store in database
			if err := ws.db.InsertMetric(trode.ID, "disk", value, timestamp); err != nil {
				LogError("Error storing disk metric: %v", err)
			}
		}

	case *monproto.MonitoringEnvelope_Network:
		sent := float64(payload.Network.TotalBytesSent) / 1024 / 1024 // MB
		recv := float64(payload.Network.TotalBytesRecv) / 1024 / 1024 // MB

		ws.chartData.Network.Timestamps = append(ws.chartData.Network.Timestamps, formattedTime)
		ws.chartData.Network.Sent = append(ws.chartData.Network.Sent, sent)
		ws.chartData.Network.Received = append(ws.chartData.Network.Received, recv)
		ws.limitNetworkData(ws.chartData.Network)

		// Store in database
		if err := ws.db.InsertMetric(trode.ID, "network_sent", sent, timestamp); err != nil {
			LogError("Error storing network sent metric: %v", err)
		}
		if err := ws.db.InsertMetric(trode.ID, "network_recv", recv, timestamp); err != nil {
			LogError("Error storing network received metric: %v", err)
		}
	}

	// Broadcast chart updates to WebSocket clients
	go ws.broadcastChartsUpdate()
}

// updateTrodeData updates the process and user data for a specific trode
func (ws *WebServer) updateTrodeData(trode *TrodeStatus, data *monproto.MonitoringEnvelope) {
	timestamp := time.Unix(0, data.Timestamp)

	switch payload := data.Payload.(type) {
	case *monproto.MonitoringEnvelope_Processes:
		trode.Processes = payload.Processes.TopProcesses
		trode.ProcessTimestamp = timestamp

		// Update global process data
		ws.dataMux.Lock()
		ws.processData.Timestamp = timestamp
		ws.processData.Processes = payload.Processes.TopProcesses
		ws.dataMux.Unlock()

		// Store in database
		for _, process := range payload.Processes.TopProcesses {
			if err := ws.db.InsertProcess(trode.ID, process, timestamp); err != nil {
				LogError("Error storing process data: %v", err)
			}
		}

	case *monproto.MonitoringEnvelope_Users:
		trode.Users = payload.Users.TopUsers
		trode.UserTimestamp = timestamp

		// Update global user data
		ws.dataMux.Lock()
		ws.userData.Timestamp = timestamp
		ws.userData.Users = payload.Users.TopUsers
		ws.dataMux.Unlock()

		// Store in database
		for _, user := range payload.Users.TopUsers {
			if err := ws.db.InsertUser(trode.ID, user, timestamp); err != nil {
				LogError("Error storing user data: %v", err)
			}
		}
	}
}

// limitChartData limits the number of data points in chart data
func (ws *WebServer) limitChartData(series *ChartSeries) {
	maxPoints := 50
	if len(series.Timestamps) > maxPoints {
		series.Timestamps = series.Timestamps[len(series.Timestamps)-maxPoints:]
		series.Values = series.Values[len(series.Values)-maxPoints:]
	}
}

// limitNetworkData limits the number of data points in network data
func (ws *WebServer) limitNetworkData(network *NetworkData) {
	if network == nil {
		return
	}

	// Initialize slices if they're nil
	if network.Timestamps == nil {
		network.Timestamps = make([]string, 0)
	}
	if network.Sent == nil {
		network.Sent = make([]float64, 0)
	}
	if network.Received == nil {
		network.Received = make([]float64, 0)
	}

	maxPoints := 50
	if len(network.Timestamps) > maxPoints {
		start := len(network.Timestamps) - maxPoints
		if start < 0 {
			start = 0
		}
		network.Timestamps = network.Timestamps[start:]

		// Only trim sent/received if they have enough data
		if len(network.Sent) > maxPoints {
			network.Sent = network.Sent[len(network.Sent)-maxPoints:]
		}
		if len(network.Received) > maxPoints {
			network.Received = network.Received[len(network.Received)-maxPoints:]
		}
	}

	// Ensure all slices have the same length
	minLen := len(network.Timestamps)
	if len(network.Sent) < minLen {
		minLen = len(network.Sent)
	}
	if len(network.Received) < minLen {
		minLen = len(network.Received)
	}

	if minLen < len(network.Timestamps) {
		network.Timestamps = network.Timestamps[len(network.Timestamps)-minLen:]
	}
	if minLen < len(network.Sent) {
		network.Sent = network.Sent[len(network.Sent)-minLen:]
	}
	if minLen < len(network.Received) {
		network.Received = network.Received[len(network.Received)-minLen:]
	}
}

// getDashboardData prepares data for the dashboard
func (ws *WebServer) getDashboardData() *DashboardData {
	ws.trodesMux.RLock()
	defer ws.trodesMux.RUnlock()

	var trodes []*TrodeStatus
	activeTrodes := 0
	totalCPU, totalMemory, totalDisk := 0.0, 0.0, 0.0
	validTrodes := 0

	for _, trode := range ws.trodes {
		trodes = append(trodes, trode)
		if trode.Online {
			activeTrodes++
			totalCPU += trode.CPU
			totalMemory += trode.Memory
			totalDisk += trode.Disk
			validTrodes++
		}
	}

	avgCPU := 0.0
	avgMemory := 0.0
	avgDisk := 0.0

	if activeTrodes > 0 {
		avgCPU = totalCPU / float64(validTrodes)
		avgMemory = totalMemory / float64(validTrodes)
		avgDisk = totalDisk / float64(validTrodes)
	}

	ws.chartMux.RLock()
	chartData := ws.chartData
	ws.chartMux.RUnlock()

	ws.dataMux.RLock()
	processData := ws.processData
	userData := ws.userData
	ws.dataMux.RUnlock()

	return &DashboardData{
		Trodes:          trodes,
		ActiveTrodes:    activeTrodes,
		AvgCPU:          avgCPU,
		AvgMemory:       avgMemory,
		AvgDisk:         avgDisk,
		ChartData:       chartData,
		ProcessData:     processData,
		UserData:        userData,
		Interval:        int(ws.interval.Milliseconds()),
		IntervalSeconds: int(ws.interval.Seconds()),
	}
}

// HTTP handlers
func (ws *WebServer) handleDashboard(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	LogVerbose("[DASHBOARD] Requested: %s", r.URL.Path)
	data := ws.getDashboardData()
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(data)
}

func (ws *WebServer) handleAPIDashboard(w http.ResponseWriter, r *http.Request) {
	LogVerbose("[API] /api/dashboard requested")
	data := ws.getDashboardData()
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(data)
}

func (ws *WebServer) handleAPIRefresh(w http.ResponseWriter, r *http.Request) {
	LogVerbose("[API] /api/refresh requested")

	// Redirect to dashboard API
	http.Redirect(w, r, "/api/dashboard", http.StatusSeeOther)
}

func (ws *WebServer) handleAPITrodes(w http.ResponseWriter, r *http.Request) {
	ws.trodesMux.RLock()
	defer ws.trodesMux.RUnlock()

	LogVerbose("[API] /api/trodes called. trodes: %d", len(ws.trodes))

	// Return JSON for all requests
	var trodes []*TrodeStatus
	for _, trode := range ws.trodes {
		trodes = append(trodes, trode)
	}
	LogVerbose("[API] Returning JSON with %d trodes", len(trodes))
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(trodes)
}

func (ws *WebServer) handleAPITrodesList(w http.ResponseWriter, r *http.Request) {
	ws.trodesMux.RLock()
	defer ws.trodesMux.RUnlock()

	LogVerbose("[API] /api/trodes/list called")

	// Return the list of configured trodes (not their status, just config)
	// For now, return the 3 hardcoded test trodes in a fixed order
	configList := &struct {
		Trodes []*struct {
			ID      string `json:"id"`
			Address string `json:"address"`
			Port    int    `json:"port"`
			Name    string `json:"name"`
			Enabled bool   `json:"enabled"`
		} `json:"trodes"`
	}{
		Trodes: []*struct {
			ID      string `json:"id"`
			Address string `json:"address"`
			Port    int    `json:"port"`
			Name    string `json:"name"`
			Enabled bool   `json:"enabled"`
		}{
			{
				ID:      "trode-1",
				Address: "localhost",
				Port:    50051,
				Name:    "Test Trode 1",
				Enabled: true,
			},
			{
				ID:      "trode-2",
				Address: "localhost",
				Port:    50052,
				Name:    "Test Trode 2",
				Enabled: true,
			},
			{
				ID:      "trode-3",
				Address: "localhost",
				Port:    50053,
				Name:    "Test Trode 3",
				Enabled: true,
			},
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(configList)
}

func (ws *WebServer) handleAPITrodesConfig(w http.ResponseWriter, r *http.Request) {
	LogVerbose("[API] /api/trodes/config called")

	if r.Method == "GET" {
		// Return current trode configuration
		ws.handleAPITrodesList(w, r)
		return
	}

	if r.Method == "POST" {
		// Update trode configuration
		var configList struct {
			Trodes []struct {
				ID      string `json:"id"`
				Address string `json:"address"`
				Port    int    `json:"port"`
				Name    string `json:"name"`
				Enabled bool   `json:"enabled"`
			} `json:"trodes"`
		}

		if err := json.NewDecoder(r.Body).Decode(&configList); err != nil {
			http.Error(w, "Invalid JSON", http.StatusBadRequest)
			return
		}

		// For now, just log the configuration update
		LogVerbose("Received trode configuration update: %+v", configList)

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{"status": "success"})
		return
	}

	http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}

func (ws *WebServer) handleAPITrodeUpdate(w http.ResponseWriter, r *http.Request) {

	// Set CORS headers
	w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
	w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
	w.Header().Set("Access-Control-Allow-Credentials", "true")

	// Handle preflight requests
	if r.Method == "OPTIONS" {
		w.WriteHeader(http.StatusOK)
		return
	}

	ws.trodesMux.RLock()
	defer ws.trodesMux.RUnlock()

	LogVerbose("[API] /api/trodes/update called")

	// Get trode ID from query parameter
	trodeID := r.URL.Query().Get("id")
	if trodeID == "" {
		http.Error(w, "Missing trode ID", http.StatusBadRequest)
		return
	}

	// Find the trode by ID
	var targetTrode *TrodeStatus
	for _, trode := range ws.trodes {
		if trode.ID == trodeID {
			targetTrode = trode
			break
		}
	}

	if targetTrode == nil {
		// Return a default offline status for the requested trode
		defaultTrode := &TrodeStatus{
			ID:         trodeID,
			Address:    "localhost",
			Port:       50051,
			Online:     false,
			CPU:        0,
			Memory:     0,
			Disk:       0,
			LastUpdate: time.Now(),
			LastError:  "Trode not found or not connected",
			Processes:  []*monproto.JobInfo{},
			Users:      []*monproto.UserInfo{},
		}
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(defaultTrode)
		return
	}

	// Ensure we have the latest process and user data
	// If the trode doesn't have recent process/user data, try to load from database
	if len(targetTrode.Processes) == 0 || time.Since(targetTrode.ProcessTimestamp) > 5*time.Minute {
		// Load recent process data from database
		end := time.Now()
		start := end.Add(-10 * time.Minute) // Last 10 minutes
		if processes, err := ws.db.GetProcesses(targetTrode.ID, start, end); err == nil && len(processes) > 0 {
			targetTrode.Processes = processes
			targetTrode.ProcessTimestamp = end
			LogVerbose("[API] Loaded %d processes from database for %s", len(processes), trodeID)
		}
	}

	if len(targetTrode.Users) == 0 || time.Since(targetTrode.UserTimestamp) > 5*time.Minute {
		// Load recent user data from database
		end := time.Now()
		start := end.Add(-10 * time.Minute) // Last 10 minutes
		if users, err := ws.db.GetUsers(targetTrode.ID, start, end); err == nil && len(users) > 0 {
			targetTrode.Users = users
			targetTrode.UserTimestamp = end
			LogVerbose("[API] Loaded %d users from database for %s", len(users), trodeID)
		}
	}

	// Return the trode update
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(targetTrode)
}

func (ws *WebServer) handleAPICharts(w http.ResponseWriter, r *http.Request) {
	// Set CORS headers
	w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
	w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
	w.Header().Set("Access-Control-Allow-Credentials", "true")

	// Handle preflight requests
	if r.Method == "OPTIONS" {
		w.WriteHeader(http.StatusOK)
		return
	}

	ws.chartMux.RLock()
	defer ws.chartMux.RUnlock()

	// Create a new ChartData instance for this request
	chartData := &ChartData{
		CPU:     &ChartSeries{Timestamps: []string{}, Values: []float64{}},
		Memory:  &ChartSeries{Timestamps: []string{}, Values: []float64{}},
		Disk:    &ChartSeries{Timestamps: []string{}, Values: []float64{}},
		Network: &NetworkData{Timestamps: []string{}, Sent: []float64{}, Received: []float64{}},
	}

	// Get time range from query parameters
	end := time.Now()
	start := end.Add(-1 * time.Hour) // Default to last hour
	if r.URL.Query().Get("range") == "day" {
		start = end.Add(-24 * time.Hour)
	}

	// Load historical data from database if needed
	if r.URL.Query().Get("historical") == "true" {
		for _, trode := range ws.trodes {
			// Load CPU metrics
			if timestamps, values, err := ws.db.GetMetrics(trode.ID, "cpu", start, end); err == nil {
				for i, ts := range timestamps {
					chartData.CPU.Timestamps = append(chartData.CPU.Timestamps, ts.Format("15:04:05"))
					chartData.CPU.Values = append(chartData.CPU.Values, values[i])
				}
			}

			// Load Memory metrics
			if timestamps, values, err := ws.db.GetMetrics(trode.ID, "memory", start, end); err == nil {
				for i, ts := range timestamps {
					chartData.Memory.Timestamps = append(chartData.Memory.Timestamps, ts.Format("15:04:05"))
					chartData.Memory.Values = append(chartData.Memory.Values, values[i])
				}
			}

			// Load Disk metrics
			if timestamps, values, err := ws.db.GetMetrics(trode.ID, "disk", start, end); err == nil {
				for i, ts := range timestamps {
					chartData.Disk.Timestamps = append(chartData.Disk.Timestamps, ts.Format("15:04:05"))
					chartData.Disk.Values = append(chartData.Disk.Values, values[i])
				}
			}

			// Load Network metrics
			if timestamps, sent, err := ws.db.GetMetrics(trode.ID, "network_sent", start, end); err == nil {
				for i, ts := range timestamps {
					chartData.Network.Timestamps = append(chartData.Network.Timestamps, ts.Format("15:04:05"))
					chartData.Network.Sent = append(chartData.Network.Sent, sent[i])
				}
			}
			if timestamps, recv, err := ws.db.GetMetrics(trode.ID, "network_recv", start, end); err == nil {
				if len(chartData.Network.Received) < len(timestamps) {
					chartData.Network.Received = make([]float64, len(timestamps))
				}
				for i := range timestamps {
					chartData.Network.Received[i] = recv[i]
				}
			}
		}
	} else {
		// For non-historical requests, use the current chart data
		chartData = ws.chartData
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(chartData)
}

func (ws *WebServer) handleAPIProcesses(w http.ResponseWriter, r *http.Request) {
	ws.dataMux.RLock()
	defer ws.dataMux.RUnlock()

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(ws.processData)
}

func (ws *WebServer) handleAPIUsers(w http.ResponseWriter, r *http.Request) {
	ws.dataMux.RLock()
	defer ws.dataMux.RUnlock()

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(ws.userData)
}

func (ws *WebServer) handleWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := ws.upgrader.Upgrade(w, r, nil)
	if err != nil {
		LogError("Error upgrading to WebSocket: %v", err)
		return
	}
	defer conn.Close()

	// Add client to the list
	ws.clientsMux.Lock()
	ws.clients[conn] = &ClientInfo{conn: conn, connected: true}
	clientCount := len(ws.clients)
	ws.clientsMux.Unlock()

	// Clean up client when handler exits
	defer func() {
		ws.clientsMux.Lock()
		delete(ws.clients, conn)
		ws.clientsMux.Unlock()
		LogVerbose("WebSocket client disconnected. Total clients: %d", len(ws.clients))
	}()

	LogVerbose("WebSocket client connected. Total clients: %d", clientCount)

	// Send initial data to the new client
	initialData := ws.getDashboardData()
	message := &WebSocketMessage{
		Type:      MessageTypeTrodesUpdate,
		Payload:   initialData,
		Timestamp: time.Now(),
	}

	messageJSON, err := json.Marshal(message)
	if err == nil {
		clientInfo := ws.getClientInfo(conn)
		if clientInfo != nil {
			clientInfo.writeMux.Lock()
			conn.WriteMessage(websocket.TextMessage, messageJSON)
			clientInfo.writeMux.Unlock()
		}
	}

	// Set up ping handler
	conn.SetPongHandler(func(string) error {
		conn.SetReadDeadline(time.Now().Add(60 * time.Second))
		return nil
	})

	// Start ping ticker
	pingTicker := time.NewTicker(30 * time.Second)
	defer pingTicker.Stop()

	// Handle incoming messages
	go func() {
		for {
			_, message, err := conn.ReadMessage()
			if err != nil {
				if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
					LogError("WebSocket read error: %v", err)
				}
				// Mark client as disconnected
				ws.clientsMux.Lock()
				if clientInfo, exists := ws.clients[conn]; exists {
					clientInfo.connected = false
				}
				ws.clientsMux.Unlock()
				break
			}

			// Handle different message types
			var wsMessage WebSocketMessage
			if err := json.Unmarshal(message, &wsMessage); err != nil {
				LogError("Error unmarshaling WebSocket message: %v", err)
				continue
			}

			switch wsMessage.Type {
			case MessageTypePing:
				// Respond with pong
				pongMessage := &WebSocketMessage{
					Type:      MessageTypePong,
					Timestamp: time.Now(),
				}
				if pongJSON, err := json.Marshal(pongMessage); err == nil {
					clientInfo := ws.getClientInfo(conn)
					if clientInfo != nil {
						clientInfo.writeMux.Lock()
						conn.WriteMessage(websocket.TextMessage, pongJSON)
						clientInfo.writeMux.Unlock()
					}
				}
			default:
				LogVerbose("Received WebSocket message: %s", wsMessage.Type)
			}
		}
	}()

	// Handle ping ticker
	for {
		select {
		case <-pingTicker.C:
			conn.SetReadDeadline(time.Now().Add(60 * time.Second))
			clientInfo := ws.getClientInfo(conn)
			if clientInfo != nil {
				clientInfo.writeMux.Lock()
				if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
					clientInfo.writeMux.Unlock()
					LogError("Error sending ping: %v", err)
					return
				}
				clientInfo.writeMux.Unlock()
			}
		}
	}
}

// Node management API handlers

// handleAPINodes handles /api/nodes endpoint for listing and creating nodes
func (ws *WebServer) handleAPINodes(w http.ResponseWriter, r *http.Request) {
	LogVerbose("[API] /api/nodes - Received %s request", r.Method)
	w.Header().Set("Content-Type", "application/json")

	switch r.Method {
	case "GET":
		LogVerbose("[API] /api/nodes GET - Listing all nodes")
		// List all nodes
		nodes, err := ws.db.GetAllNodes()
		if err != nil {
			LogError("[API] /api/nodes GET - Error retrieving nodes: %v", err)
			http.Error(w, fmt.Sprintf("Error retrieving nodes: %v", err), http.StatusInternalServerError)
			return
		}

		// Hide sensitive information (SSH keys and passwords)
		for _, node := range nodes {
			node.SSHKeyPath = ""
			node.SSHPassword = ""
		}

		json.NewEncoder(w).Encode(map[string]interface{}{
			"success": true,
			"nodes":   nodes,
		})

	case "POST":
		LogVerbose("[API] /api/nodes POST - Creating new node")
		
		// Create a new node
		var node db.Node
		if err := json.NewDecoder(r.Body).Decode(&node); err != nil {
			LogError("[API] /api/nodes POST - Error decoding request: %v", err)
			http.Error(w, fmt.Sprintf("Error decoding request: %v", err), http.StatusBadRequest)
			return
		}

		LogVerbose("[API] /api/nodes POST - Received node data: Name=%s, Address=%s, SSHUser=%s, SSHPort=%d", 
			node.Name, node.Address, node.SSHUser, node.SSHPort)

		// Validate required fields
		if node.Name == "" || node.Address == "" || node.SSHUser == "" {
			LogError("[API] /api/nodes POST - Missing required fields: Name=%s, Address=%s, SSHUser=%s", 
				node.Name, node.Address, node.SSHUser)
			http.Error(w, "Name, address, and SSH user are required", http.StatusBadRequest)
			return
		}

		// Check if node name already exists
		exists, err := ws.db.NodeExists(node.Name)
		if err != nil {
			LogError("[API] /api/nodes POST - Error checking node existence: %v", err)
			http.Error(w, fmt.Sprintf("Error checking node existence: %v", err), http.StatusInternalServerError)
			return
		}
		if exists {
			LogError("[API] /api/nodes POST - Node with name '%s' already exists", node.Name)
			http.Error(w, "Node with this name already exists", http.StatusConflict)
			return
		}

		// Set default values
		if node.SSHPort == 0 {
			node.SSHPort = 22
		}
		if node.RemoteDir == "" {
			node.RemoteDir = "/opt/gollect-trode"
		}

		LogVerbose("[API] /api/nodes POST - Inserting node with final config: Name=%s, Address=%s, SSHUser=%s, SSHPort=%d, RemoteDir=%s", 
			node.Name, node.Address, node.SSHUser, node.SSHPort, node.RemoteDir)

		// Insert the node
		if err := ws.db.InsertNode(&node); err != nil {
			LogError("[API] /api/nodes POST - Error inserting node: %v", err)
			http.Error(w, fmt.Sprintf("Error creating node: %v", err), http.StatusInternalServerError)
			return
		}

		LogVerbose("[API] /api/nodes POST - Successfully created node with ID: %d", node.ID)

		// Return the created node (without sensitive data)
		node.SSHKeyPath = ""
		node.SSHPassword = ""

		json.NewEncoder(w).Encode(map[string]interface{}{
			"success": true,
			"node":    node,
		})

	default:
		LogError("[API] /api/nodes - Method not allowed: %s", r.Method)
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

// handleAPINode handles /api/nodes/{id} endpoint for updating and deleting specific nodes
func (ws *WebServer) handleAPINode(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	// Extract node ID from URL path
	pathParts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(pathParts) < 3 {
		http.Error(w, "Invalid node ID", http.StatusBadRequest)
		return
	}

	nodeID, err := strconv.Atoi(pathParts[2])
	if err != nil {
		http.Error(w, "Invalid node ID", http.StatusBadRequest)
		return
	}

	switch r.Method {
	case "GET":
		// Get a specific node
		node, err := ws.db.GetNode(nodeID)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error retrieving node: %v", err), http.StatusInternalServerError)
			return
		}

		// Hide sensitive information
		node.SSHKeyPath = ""
		node.SSHPassword = ""

		json.NewEncoder(w).Encode(map[string]interface{}{
			"success": true,
			"node":    node,
		})

	case "PUT":
		// Update a node
		var node db.Node
		if err := json.NewDecoder(r.Body).Decode(&node); err != nil {
			http.Error(w, fmt.Sprintf("Error decoding request: %v", err), http.StatusBadRequest)
			return
		}

		// Set the ID from the URL
		node.ID = nodeID

		// Validate required fields
		if node.Name == "" || node.Address == "" || node.SSHUser == "" {
			http.Error(w, "Name, address, and SSH user are required", http.StatusBadRequest)
			return
		}

		// Check if the new name conflicts with another node
		existingNode, err := ws.db.GetNodeByName(node.Name)
		if err == nil && existingNode.ID != nodeID {
			http.Error(w, "Node with this name already exists", http.StatusConflict)
			return
		}

		// Set default values
		if node.SSHPort == 0 {
			node.SSHPort = 22
		}
		if node.RemoteDir == "" {
			node.RemoteDir = "/opt/gollect-trode"
		}

		// Update the node
		if err := ws.db.UpdateNode(&node); err != nil {
			http.Error(w, fmt.Sprintf("Error updating node: %v", err), http.StatusInternalServerError)
			return
		}

		// Return the updated node (without sensitive data)
		node.SSHKeyPath = ""
		node.SSHPassword = ""

		json.NewEncoder(w).Encode(map[string]interface{}{
			"success": true,
			"node":    node,
		})

	case "DELETE":
		// Delete a node
		if err := ws.db.DeleteNode(nodeID); err != nil {
			http.Error(w, fmt.Sprintf("Error deleting node: %v", err), http.StatusInternalServerError)
			return
		}

		json.NewEncoder(w).Encode(map[string]interface{}{
			"success": true,
			"message": "Node deleted successfully",
		})

	default:
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

// handleAPINodeValidation handles SSH credential validation
func (ws *WebServer) handleAPINodeValidation(w http.ResponseWriter, r *http.Request) {
	LogVerbose("[API] /api/nodes/validate - Received %s request", r.Method)
	w.Header().Set("Content-Type", "application/json")

	if r.Method != "POST" {
		LogError("[API] /api/nodes/validate - Method not allowed: %s", r.Method)
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Parse the node data from request body
	var node db.Node
	if err := json.NewDecoder(r.Body).Decode(&node); err != nil {
		LogError("[API] /api/nodes/validate - Error decoding request: %v", err)
		http.Error(w, fmt.Sprintf("Error decoding request: %v", err), http.StatusBadRequest)
		return
	}

	LogVerbose("[API] /api/nodes/validate - Validating SSH credentials for node: %s@%s:%d", 
		node.SSHUser, node.Address, node.SSHPort)

	// Validate required fields
	if node.Address == "" || node.SSHUser == "" {
		LogError("[API] /api/nodes/validate - Missing required fields: Address=%s, SSHUser=%s", 
			node.Address, node.SSHUser)
		http.Error(w, "Address and SSH user are required", http.StatusBadRequest)
		return
	}

	// Set default SSH port if not provided
	if node.SSHPort == 0 {
		node.SSHPort = 22
	}

	// Validate SSH credentials
	validationResult, err := ws.db.ValidateSSHCredentials(&node)
	if err != nil {
		LogError("[API] /api/nodes/validate - Error during validation: %v", err)
		http.Error(w, fmt.Sprintf("Error during validation: %v", err), http.StatusInternalServerError)
		return
	}

	LogVerbose("[API] /api/nodes/validate - Validation result: Valid=%t, HasSudo=%t, Message=%s", 
		validationResult.Valid, validationResult.HasSudo, validationResult.Message)

	// Return the validation result
	json.NewEncoder(w).Encode(map[string]interface{}{
		"success": true,
		"validation": validationResult,
	})
}
